package com.wanger.swc.config.myBatisPlus.autoDDL;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ModifierUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.wanger.swc.config.myBatisPlus.autoDDL.model.*;
import com.wanger.swc.exception.BusEx;
import com.wanger.swc.model.tips.TipInfo;
import com.wanger.swc.util.validation.MyVerifyUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.anyline.metadata.Column;
import org.anyline.metadata.PrimaryKey;
import org.anyline.metadata.Table;
import org.anyline.metadata.type.DatabaseType;
import org.anyline.proxy.ServiceProxy;
import org.anyline.service.AnylineService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author: wanger
 * @Date: 2025/1/17 15:03
 * @Description:
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AutoDDLDispatcher {

    private final AutoDDlConfig config;
    private final ApplicationContext applicationContext;

    /**
     * 启动要执行的任务
     */
    public void start() {
        try {
            startImpl();
        } catch (Exception e) {
            //告知，但不要阻止程序启动！
            log.warn("自动DDL执行失败，项目的数据库配置可能存在问题！", e);
            log.warn("如果配置的数据库没有问题请忽略这个警告!");
        }
    }

    /**
     * 实际调度的方法
     */
    public void startImpl() {
        //获取数据库连接
        AnylineService<?> dbService = ServiceProxy.service();
        //获取带@TableName注解的类与字段,并处理为Map《表名，{表明，表注释，[{字段java class类型，字段名称，字段注释}]}》
        Map<String, AutoDDlTable> entityMap = getAllEntityTable();
        List<MyTable> allTable = getAllTable(dbService);

        //记录数据库有但代码有的       -》不予处理
        List<AutoDDlTable> list1 = new ArrayList<>();
        //记录数据库有但代码没有的     -》收集后警告
        List<String> list2 = new ArrayList<>();

        //调用该数据库类型对应的查询表结构与表字段的方法
        for (MyTable table : allTable) {
            //1.检查该表是否有实体类
            AutoDDlTable entity = entityMap.get(table.getOriginalTableName());
            if (entity != null) {//库有代码中有，则对比字段
                Set<String> set1 = new HashSet<>();//记录都有的
                Set<String> set2 = new HashSet<>();//记录数据库有，代码没有的
                Map<String, AutoDDLColumn> columnMap = entity.getColumnMap();
                //记录需要执行的表明
                for (MyColumn column : table.getColumns()) {
                    AutoDDLColumn ddlColumn = columnMap.get(column.getColumnName());
                    if (ddlColumn != null) set1.add(column.getColumnName());//数据库有字段，代码已有不予处理
                    else set2.add(column.getColumnName());//数据库有字段，代码没有
                }
                list1.add(new AutoDDlTable(entity.getEntity(),
                        entity.getTableName(),
                        entity.getTableComment(),
                        columnMap.values()
                                .stream()
                                .filter(c -> !set1.contains(c.getColumnName()))
                                .collect(Collectors.toMap(AutoDDLColumn::getColumnName,
                                        Function.identity(),
                                        (v1, v2) -> v1,
                                        LinkedHashMap::new)),
                        set2));
            } else {//库有，代码没有，则追加表后追加字段
                list2.add(table.getOriginalTableName());
            }
        }
        //记录数据库没有，但代码有的情况
        Set<String> set3 = list1.stream().map(AutoDDlTable::getTableName).collect(Collectors.toSet());

        //记录数据库没有但代码有的     -》收集后根据配置，true：执行后提示，false：打印ddl语句
        List<AutoDDlTable> list3 = entityMap.values().stream().filter(e -> !set3.contains(e.getTableName())).collect(Collectors.toList());

        fieldDdl(dbService, list1);
        processingWarning(list2);
        tableDdl(dbService, list3);
    }

    /**
     * 获取数据库所有表
     *
     * @param dbService 数据库服务
     * @return 数据库所有表
     */
    private List<MyTable> getAllTable(AnylineService<?> dbService) {
        //获取查询表结构的服务
        AutoDDLService adService = getAutoDDLService(dbService);
        //根据服务执行获取表结构
        List<MyTable> allTable = adService.getAllTable(dbService);
        Set<String> ignoreTable = config.getIgnoreTable();
        Set<String> ignoreTableUpperCase = ignoreTable.stream().filter(StrUtil::isNotBlank).map(String::toUpperCase).collect(Collectors.toSet());
        Set<String> ignoreTableLowerCase = ignoreTable.stream().filter(StrUtil::isNotBlank).map(String::toLowerCase).collect(Collectors.toSet());
        //按配置过滤
        return allTable.stream()
                .filter(it -> !(ignoreTableUpperCase.contains(it.getOriginalTableName()) || ignoreTableLowerCase.contains(it.getOriginalTableName())))
                .collect(Collectors.toList());
    }

    /**
     * 处理数据库有表，代码中也有entity，但字段不一定 一一对应的情况
     *
     * @param dbService 数据库连接
     * @param list      l
     */
    private void fieldDdl(AnylineService<?> dbService, List<AutoDDlTable> list) {
        AutoDDLService adService = getAutoDDLService(dbService);

        //收集需要警告提示的内容
        Map<String, Set<String>> warningMap = new LinkedHashMap<>();
        for (AutoDDlTable table : list) {
            Map<String, AutoDDLColumn> columnMap = table.getColumnMap();
            for (AutoDDLColumn column : columnMap.values()) {
                addColumn(dbService,
                        table.getTableName(),
                        column.getColumnName(),
                        adService.getDataType(column.getJavaType()),
                        column.getColumnComment(),
                        column.primary());
            }
            warningMap.put(table.getTableName(), table.getColumnSet());
        }
        processFieldWarning(warningMap);
    }

    private void addColumn(AnylineService<?> dbService, String table, String name, String type, String comment, boolean isPrimary) {
        if (!config.isExecuteDdl()) return;//不执行则直接返回
        try {
            Column column = buildColumn(table, name, type, comment);
            dbService.ddl().add(column);
            if (isPrimary) {
                PrimaryKey key = new PrimaryKey();
                key.setTable(table);
                key.addColumn(column);
                dbService.ddl().add(key);
            }
        } catch (Exception e) {
            log.error("创建{}.{}字段失败！", table, name, e);
        }
    }

    private Column buildColumn(String table, String name, String type, String comment) {
        Column column = new Column(new Table<>(table), name, type);
        if (StrUtil.isNotBlank(comment)) column.setComment(comment);
        return column;
    }

    /**
     * 数据库有字段，但代码中没有的，打印警告！格式【表table_demo的[]字段在Entity中未找到】
     *
     * @param warningMap w
     */
    private static void processFieldWarning(Map<String, Set<String>> warningMap) {
        if (CollUtil.isNotEmpty(warningMap)) {
            warningMap.forEach((k, v) -> {
                if (CollUtil.isNotEmpty(v)) log.warn("表{}的[{}]字段在Entity中未找到", k, StrUtil.join(",", v));
            });
        }
    }

    /**
     * 处理数据库中有，但代码没有的情况，警告处理【[test]表未找到对应的Entity】
     *
     * @param list l
     */
    private static void processingWarning(List<String> list) {
        if (CollUtil.isNotEmpty(list)) log.warn("表【{}】未找到对应的Entity", StrUtil.join(",", list));
    }

    /**
     * 处理数据库没有，但代码中有的情况->收集后根据配置，true：执行后提示，false：打印ddl语句
     *
     * @param dbService 数据库连接
     * @param list      l
     */
    private void tableDdl(AnylineService<?> dbService, List<AutoDDlTable> list) {
        if (!config.isExecuteDdl()) return;//不执行则直接返回
        AutoDDLService adService = getAutoDDLService(dbService);

        for (AutoDDlTable adTable : list) {
            Collection<AutoDDLColumn> ddlColumns = adTable.getColumnMap().values();
            if (CollUtil.isEmpty(ddlColumns)) continue;//字段为空，则表也不创建
            Table<?> table = new Table<>(adTable.getTableName());
            if (StrUtil.isNotBlank(adTable.getTableComment())) table.setComment(adTable.getTableComment());
            PrimaryKey key = new PrimaryKey();
            key.setTable(adTable.getTableName());
            for (AutoDDLColumn adColumn : ddlColumns) {
                Column column = buildColumn(adTable.getTableName(),
                        adColumn.getColumnName(),
                        adService.getDataType(adColumn.getJavaType()),
                        adColumn.getColumnComment());
                table.addColumn(column);
                if (adColumn.primary()) key.addColumn(column);
            }
            if (CollUtil.isNotEmpty(key.getColumns())) table.setPrimaryKey(key);
            try {
                dbService.ddl().create(table);
            } catch (Exception e) {
                log.error("创建{}表失败！", adTable.getTableName(), e);
            }
        }
    }

    /**
     * 获取所有entity
     *
     * @return entity封装
     */
    private Map<String, AutoDDlTable> getAllEntityTable() {
        // 扫描
        List<Class<?>> classList = scanForTableNameAnnotatedClasses();
        // 转换
        return classToTable(classList);
    }

    /**
     * 转class到entity
     *
     * @param classList class集合
     * @return entity封装
     */
    private Map<String, AutoDDlTable> classToTable(List<Class<?>> classList) {
        //读取@TableName注解的value值，若为空，则读取类名，再转下划线命名法
        List<AutoDDlTable> list = new ArrayList<>();
        for (Class<?> ac : classList) {
            TableName tableName = ac.getAnnotation(TableName.class);
            ApiModel apiModel = ac.getAnnotation(ApiModel.class);

            List<AutoDDLColumn> columnList = new ArrayList<>();
            List<Field> fields = getAllFields(ac);
            for (Field field : fields) {
                if (ModifierUtil.isStatic(field) || Modifier.isTransient(field.getModifiers())) continue;
                TableField tableField = field.getAnnotation(TableField.class);//取@TableField注解的value值进行覆盖
                String fieldName = tableField != null && tableField.exist() && StrUtil.isNotBlank(tableField.value()) ?
                        tableField.value() :
                        StrUtil.toUnderlineCase(field.getName());
                ApiModelProperty amp = field.getAnnotation(ApiModelProperty.class);
                String columnComment = amp != null ? amp.value() : null;
                columnList.add(new AutoDDLColumn(field, fieldName, columnComment));
            }
            if (tableName != null) list.add(new AutoDDlTable(ac,
                    tableName.value(),
                    apiModel != null ? apiModel.value() : null,
                    columnList.stream()
                            .collect(Collectors.toMap(
                                    AutoDDLColumn::getColumnName,
                                    Function.identity(),
                                    (v1, v2) -> v1,
                                    LinkedHashMap::new))));
        }

        return list.stream()
                //过滤要忽略的entity
                .filter(adTable -> !config.getIgnoreEntity().contains(adTable.getEntity()))
                //转换为map
                .collect(Collectors.toMap(AutoDDlTable::getTableName,
                        Function.identity(),
                        (v1, v2) -> v1));
    }

    /**
     * 递归方法收集所有字段
     *
     * @param clazz 类型
     * @return 字段集合
     */
    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();

        // 获取当前类的字段
        if (clazz != null) {
            // 递归获取父类的字段
            Class<?> superclass = clazz.getSuperclass();
            if (superclass != null && !superclass.equals(Object.class)) {
                fields.addAll(getAllFields(superclass));  // 递归调用
            }
            // 获取当前类所有声明的字段（包括私有字段）
            //Collections.addAll(fields, );
            fields.addAll(Arrays.stream(clazz.getDeclaredFields())
                    .filter(field -> !ModifierUtil.isSynthetic(field))
                    .collect(Collectors.toList()));
        }

        return fields;
    }

    /**
     * 扫描所有带@TableName注解的类
     *
     * @return class集合
     */
    private List<Class<?>> scanForTableNameAnnotatedClasses() {
        // 需要扫描的包路径

        // 创建类路径扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);

        // 过滤器：只扫描带有 @TableName 注解的类
        TypeFilter filter = new AnnotationTypeFilter(TableName.class);
        scanner.addIncludeFilter(filter);
        // 扫描指定包路径中的类
        return scanner.findCandidateComponents(config.getScanPackage())
                .parallelStream()
                .map(it -> {
                    try {
                        return Class.forName(it.getBeanClassName());
                    } catch (ClassNotFoundException e) {
                        throw BusEx.create("扫描实体类出问题了！");
                    }
                }).collect(Collectors.toList());
    }

    /**
     * 根据数据库类型获取对应的自动ddl实现类
     *
     * @param dbService 数据库类型
     * @return 实现类
     */
    private AutoDDLService getAutoDDLService(AnylineService<?> dbService) {
        MyVerifyUtil.nullVerify(dbService, "数据库服务不能为空！");
        return getService(dbService.metadata().type());
    }

    /**
     * 获取页面展示的支持类型！
     *
     * @return 支持类型
     */
    public List<TipInfo> uiSupportType() {
        return applicationContext.getBeansOfType(AutoDDLService.class)
                .values()
                .stream()
                .map(e -> new TipInfo(e.getContentType().getType(), e.getContentType().getDesc()))
                .collect(Collectors.toList());
    }

    /**
     * 根据类型获取对应的实现类
     *
     * @param dbType 类型
     * @return 实现服务
     */
    public AutoDDLService getService(DatabaseType dbType) {
        MyVerifyUtil.nullVerify(dbType, "类型不能为空！");
        Map<String, AutoDDLService> dsMap = applicationContext.getBeansOfType(AutoDDLService.class);
        for (AutoDDLService value : dsMap.values()) {
            if (value.getContentType().getDbType().equals(dbType)) return value;
        }
        throw BusEx.create("不支持的类型【{}】！", dbType.title());
    }

    /**
     * 从数据库结构获取表结构的json数据对象
     *
     * @param dbId           数据源key
     * @param exclusionTable 排除的表
     * @param tablePrefix    表前缀
     * @return json数据对象
     */
    public Object getJsonData(String dbId, String exclusionTable, String tablePrefix) {
        MyVerifyUtil.nullVerify(dbId, "数据源key不能为空！");
        AnylineService<?> dbService = ServiceProxy.service(dbId);
        MyVerifyUtil.nullVerify(dbService, "数据源【{}】未注册！", dbId);
        AutoDDLService adService = this.getService(dbService.metadata().type());
        return adService.getJsonData(dbService, exclusionTable, tablePrefix);
    }
}
